//  This file is part of NHibernate.ReLinq.Sample a sample showing
//  the use of the open source re-linq library to implement a non-trivial 
//  Linq-provider, on the example of NHibernate (www.nhibernate.org).
//  Copyright (C) 2005-2009 rubicon informationstechnologie gmbh, www.rubicon.eu
//  
//  NHibernate.ReLinq.Sample is based on re-motion re-linq (http://www.re-motion.org/).
//  
//  NHibernate.ReLinq.Sample is free software; you can redistribute it 
//  and/or modify it under the terms of the MIT License 
// (http://www.opensource.org/licenses/mit-license.php).
// 
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;
using Remotion.Linq.Clauses.Expressions;
using Remotion.Linq.Clauses.ExpressionTreeVisitors;
using Remotion.Linq.Parsing;
using ScrappyDB.Utilities;

namespace ScrappyDB.Linq.SdbQueryGeneration
{
    public class SqlGeneratorExpressionTreeVisitor : ThrowingExpressionTreeVisitor
    {
        public static string GetHqlExpression(Expression linqExpression, ParameterAggregator parameterAggregator)
        {
            var visitor = new SqlGeneratorExpressionTreeVisitor(parameterAggregator);
            visitor.VisitExpression(linqExpression);
            return visitor.GetHqlExpression();
        }

        private readonly StringBuilder _hqlExpression = new StringBuilder();
        private readonly ParameterAggregator _parameterAggregator;

        private SqlGeneratorExpressionTreeVisitor(ParameterAggregator parameterAggregator)
        {
            _parameterAggregator = parameterAggregator;
        }

        public string GetHqlExpression()
        {
            return _hqlExpression.ToString();
        }

        protected override Expression VisitQuerySourceReferenceExpression(QuerySourceReferenceExpression expression)
        {
            //_hqlExpression.Append (expression.ReferencedQuerySource.ItemName);
            return expression;
        }

        protected override Expression VisitBinaryExpression(BinaryExpression expression)
        {
            //_hqlExpression.Append ("(");

            VisitExpression(expression.Left);

            // In production code, handle this via lookup tables.
            switch (expression.NodeType)
            {
                case ExpressionType.Equal:
                    _hqlExpression.Append(" = ");
                    break;

                case ExpressionType.NotEqual:
                    _hqlExpression.Append(" != ");
                    break;

                case ExpressionType.GreaterThan:
                    _hqlExpression.Append(" > ");
                    break;

                case ExpressionType.LessThan:
                    _hqlExpression.Append(" < ");
                    break;

                case ExpressionType.GreaterThanOrEqual:
                    _hqlExpression.Append(" >= ");
                    break;

                case ExpressionType.LessThanOrEqual:
                    _hqlExpression.Append(" <= ");
                    break;

                case ExpressionType.AndAlso:
                    _hqlExpression.Append(" intersection ");
                    break;

                case ExpressionType.And:
                    _hqlExpression.Append(" and ");
                    break;

                case ExpressionType.OrElse:
                case ExpressionType.Or:
                    _hqlExpression.Append(" or ");
                    break;

                //case ExpressionType.Add:
                //  _hqlExpression.Append (" + ");
                //  break;

                //case ExpressionType.Subtract:
                //  _hqlExpression.Append (" - ");
                //  break;

                //case ExpressionType.Multiply:
                //  _hqlExpression.Append (" * ");
                //  break;

                //case ExpressionType.Divide:
                //  _hqlExpression.Append (" / ");
                //  break;

                default:
                    base.VisitBinaryExpression(expression);
                    break;
            }

            VisitExpression(expression.Right);
            //_hqlExpression.Append (")");

            return expression;
        }

        protected override Expression VisitMemberExpression(MemberExpression expression)
        {
            string memberName;
            var instance = Activator.CreateInstance(expression.Member.DeclaringType);
            if (expression.Member.Name == EntityHelper.GetItemNamePropertyName(instance))
            {
                //if this is the key/id/itemname field then use the "itemName" keyword
                memberName = "itemname()";
            }
            else
            {
                //otherwise use the field name
                memberName = String.Format("`{0}`", expression.Member.Name);
            }
            VisitExpression(expression.Expression);
            _hqlExpression.Append(memberName);

            return expression;
        }


        private string EncodeValue(ConstantExpression expression)
        {
            if (expression.Value.GetType() == typeof(ushort))
                return Utility.EncodeNumeric(((ushort)expression.Value));

            if (expression.Value.GetType() == typeof(int))
                return Utility.EncodeNumeric(((int)expression.Value));

            if (expression.Value.GetType() == typeof(ulong))
                return Utility.EncodeNumeric(((ulong)expression.Value));

            //leave the value "as is"
            return expression.Value.ToString();
        }

        protected override Expression VisitConstantExpression(ConstantExpression expression, bool addQuotes)
        {


            if (addQuotes)
            {
                if (expression.Value.GetType() == typeof(string[]))
                {
                    int counter = 0;
                    int count = ((string[])expression.Value).Length;

                    foreach (string value in (string[])expression.Value)
                    {
                        //for now we are assuming that all multi-value properties are STRING ONLY so encoding is not an issue...
                        _hqlExpression.AppendFormat("'{0}'", value);
                        counter++;
                        if (counter < count)
                            _hqlExpression.AppendFormat(",");
                    }
                }
                else
                {
                    _hqlExpression.AppendFormat("'{0}'", EncodeValue(expression));

                }
            }
            else
            {
                _hqlExpression.AppendFormat("{0}", EncodeValue(expression));
            }

            return expression;
        }

        protected override Expression VisitMethodCallExpression(MethodCallExpression expression)
        {
            var methodName = ((System.Reflection.MemberInfo)(expression.Method)).Name;
            switch (methodName)
            {
                case "StartsWith":
                    VisitExpression(expression.Object);
                    _hqlExpression.Append(" like '");
                    VisitExpression(expression.Arguments[0], false);
                    _hqlExpression.Append("%'");
                    return expression;

                case "Contains":
                    VisitExpression(expression.Object);
                    _hqlExpression.Append(" like '%");
                    VisitExpression(expression.Arguments[0], false);
                    _hqlExpression.Append("%'");
                    return expression;

                case "EndsWith":
                    VisitExpression(expression.Object);
                    _hqlExpression.Append(" like '%");
                    VisitExpression(expression.Arguments[0], false);
                    _hqlExpression.Append("'");
                    return expression;

                case "GreaterThan":
                    VisitExpression(expression.Arguments[0]);
                    _hqlExpression.Append(" > ");
                    VisitExpression(expression.Arguments[1]);
                    return expression;

                case "GreaterThanEqual":
                    VisitExpression(expression.Arguments[0]);
                    _hqlExpression.Append(" >= ");
                    VisitExpression(expression.Arguments[1]);
                    return expression;

                case "LessThan":
                    VisitExpression(expression.Arguments[0]);
                    _hqlExpression.Append(" < ");
                    VisitExpression(expression.Arguments[1]);
                    return expression;

                case "LessThanEqual":
                    VisitExpression(expression.Arguments[0]);
                    _hqlExpression.Append(" <= ");
                    VisitExpression(expression.Arguments[1]);
                    return expression;

                case "Between":
                    VisitExpression(expression.Arguments[0]);
                    _hqlExpression.Append(" between ");
                    VisitExpression(expression.Arguments[1]);
                    _hqlExpression.Append(" and ");
                    VisitExpression(expression.Arguments[2]);
                    return expression;

                case "In":
                    VisitExpression(expression.Arguments[0]);
                    _hqlExpression.Append(" in (");
                    VisitExpression(expression.Arguments[1]);
                    _hqlExpression.Append(")");
                    return expression;

                case "IsNull":
                    VisitExpression(expression.Arguments[0]);
                    _hqlExpression.Append(" is null");
                    return expression;

                case "IsNotNull":
                    VisitExpression(expression.Arguments[0]);
                    _hqlExpression.Append(" is not null");
                    return expression;

                case "Every":
                    _hqlExpression.Append("every(");
                    VisitExpression(expression.Arguments[0]);

                    if (((string[])((ConstantExpression) expression.Arguments[1]).Value).Length > 1)
                    {
                        _hqlExpression.Append(") in ( ");
                        VisitExpression(expression.Arguments[1]);
                        _hqlExpression.Append(")");
                    }
                    else
                    {

                        _hqlExpression.Append(") = ");
                        VisitExpression(expression.Arguments[1]);
                    }
                    return expression;

                case "Any":
                    VisitExpression(expression.Arguments[0]);
                    _hqlExpression.Append(" = ");
                    VisitExpression(expression.Arguments[1]);
                    return expression;

                default:
                    throw new ArgumentException("Method {0} not supported yet... FIX ME!");
            }
        }

        // Called when a LINQ expression type is not handled above.
        protected override Exception CreateUnhandledItemException<T>(T unhandledItem, string visitMethod)
        {
            string itemText = FormatUnhandledItem(unhandledItem);
            var message = string.Format("The expression '{0}' (type: {1}) is not supported by this LINQ provider.", itemText, typeof(T));
            return new NotSupportedException(message);
        }

        private string FormatUnhandledItem<T>(T unhandledItem)
        {
            var itemAsExpression = unhandledItem as Expression;
            return itemAsExpression != null ? FormattingExpressionTreeVisitor.Format(itemAsExpression) : unhandledItem.ToString();
        }
    }
}